/*
 * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The OpenAirInterface Software Alliance licenses this file to You under
 * the OAI Public License, Version 1.1  (the "License"); you may not use this file
 * except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.openairinterface.org/?page_id=698
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *-------------------------------------------------------------------------------
 * For more information about the OpenAirInterface (OAI) Software Alliance:
 *      contact@openairinterface.org
 */

/*! \file uint_uid_generator.hpp
   \author  Lionel GAUTHIER
   \date 2019
   \email: lionel.gauthier@eurecom.fr
*/

#ifndef FILE_UINT_GENERATOR_HPP_SEEN
#define FILE_UINT_GENERATOR_HPP_SEEN

#include <mutex>
#include <set>

namespace util {

  template <class UINT> class uint_generator {
  private:
    UINT                       uid_generator;
    std::mutex                 m_uid_generator;

    std::set<UINT>             uid_generated;
    std::mutex                 m_uid_generated;


  public:
    uint_generator() : m_uid_generator() , m_uid_generated() {
      uid_generator = 0;
      uid_generated = {};
    };

    uint_generator(uint_generator const&) = delete;
    void operator=(uint_generator const&) = delete;

    UINT get_uid()
    {
      std::unique_lock<std::mutex> lr(m_uid_generator);
      UINT uid = ++uid_generator;
      while (true) {
        // may happen race conditions here
        std::unique_lock<std::mutex> ld(m_uid_generated);
        if (uid_generated.count(uid) == 0) {
          uid_generated.insert(uid);
          ld.unlock();
          lr.unlock();
          return uid;
        }
        uid = ++uid_generator;
      }
    }

    void free_uid(UINT uid)
    {
      std::unique_lock<std::mutex> l(m_uid_generated);
      uid_generated.erase(uid);
      l.unlock();
    }
  };

  template <class UINT> class uint_uid_generator {
  private:
    UINT                   uid_generator;
    std::mutex             m_uid_generator;

    std::set<UINT>         uid_generated;
    std::mutex             m_uid_generated;

    uint_uid_generator() : m_uid_generator() , m_uid_generated() {
      uid_generator = 0;
      uid_generated = {};
    };

  public:
    static uint_uid_generator& get_instance()
    {
      static uint_uid_generator  instance;
      return instance;
    }

    uint_uid_generator(uint_uid_generator const&) = delete;
    void operator=(uint_uid_generator const&) = delete;

    UINT get_uid()
    {
      std::unique_lock<std::mutex> lr(m_uid_generator);
      UINT uid = ++uid_generator;
      while (true) {
        // may happen race conditions here
        std::unique_lock<std::mutex> ld(m_uid_generated);
        if (uid_generated.count(uid) == 0) {
          uid_generated.insert(uid);
          lr.unlock();
          ld.unlock();
          return uid;
        }
        uid = ++uid_generator;
      }
    }

    void free_uid(UINT uid)
    {
      std::unique_lock<std::mutex> l(m_uid_generated);
      uid_generated.erase(uid);
      l.unlock();
    }
  };

}
#endif // FILE_UINT_GENERATOR_HPP_SEEN
